#include <windows.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>

const char BLOCK = '@';
const char FOOD = '*';
#define MAXLEN   20
#define MIN_X    3
#define MAX_X    44
#define MIN_Y    2
#define MAX_Y    23
#define INITLEN  5

void game(void);

typedef struct Point {
    int x, y;
} Point;

/*
    采用循环队列实现贪吃蛇
    head指向蛇头，tail指向蛇尾
    可以在o(1)时间内更新，而不需要每次浪费o(n)时间在绘制贪吃蛇上面
*/
Point snake[MAXLEN];
int head = 0, tail = 0;
int curLen, maxLen;
int faceX = 1, faceY = 0;
Point food;
int score;

int sleepTime;
int scoreGain;
int highScore;
//使用valid数组来标记位置是否合法
bool valid[MAX_X + 1][MAX_Y + 1];
//打印字符
void gotoxy(int x, int y) {
    COORD pos = {x, y};
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}
//设置颜色
void set_color(int color) {
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
}
//读取文件
void File_out() {
    FILE *f = fopen("save.txt", "r");
    if (f) {
        fscanf(f, "%d", &highScore);
        fclose(f);
    } else {
        highScore = 0;
    }
}
//创建地图
void createMap() {
    memset(valid, 1, sizeof(valid));
    system("cls");
    set_color(7);
    for(int x = MIN_X; x <= MAX_X; ++x) {
        valid[x][MIN_Y] = 0;
        valid[x][MAX_Y] = 0;
        gotoxy(x, MIN_Y);
        printf("%c", BLOCK);
        gotoxy(x, MAX_Y);
        printf("%c", BLOCK);
    }
    for (int y = MIN_Y; y <= MAX_Y; y++) {
        valid[MIN_X][y] = 0;
        valid[MAX_X][y] = 0;
        gotoxy(MIN_X, y);
        printf("%c", BLOCK);
        gotoxy(MAX_X, y);
        printf("%c", BLOCK);
    }
}
//初始化蛇
void initsnake() {
    curLen = maxLen = INITLEN;
    int posX = 24, posY = 8;

    head = tail = 0;
    for(int i = 0; i < INITLEN; i++) {
        snake[i].x = posX + i;
        snake[i].y = posY;
        valid[posX + i][posY] = 0;
    }
    head = INITLEN - 1;

    set_color(14);
    gotoxy(posX, posY);
    for (int i = 0; i < INITLEN; i++) {
        gotoxy(snake[i].x, snake[i].y);
        printf("%c", BLOCK);
    }
    set_color(7);
}
//咬到自己
bool biteself() {
    for(int i = 0, idx = tail; i < curLen - 1; i ++, idx = (idx + 1) % MAXLEN) {
        if(snake[idx].x == snake[head].x && snake[idx].y == snake[head].y) {
            return 1;
        }
    }
    return 0;
}
//撞到墙
bool cantcrosswall() {
    int x = snake[head].x;
    int y = snake[head].y;
    return (x == MIN_X || x == MAX_X || y == MIN_Y || y == MAX_Y);
}
//加速
void speedup() {
    if(sleepTime > 20) {
        sleepTime -= 30;
        scoreGain += 2;
    }
}
//加速
void speeddown() {
    if(sleepTime < 350) {
        sleepTime += 30;
        scoreGain -= 2;
    }
}
//生成食物
void createfood() {
    srand(time(0));
    while(1) {
        int x = rand() % (MAX_X - MIN_X - 1) + MIN_X + 1;
        int y = rand() % (MAX_Y - MIN_Y - 1) + MIN_Y + 1;
        if(!valid[x][y]) continue;
        food.x = x, food.y = y;
        valid[x][y] = 0;
        set_color(14);
        gotoxy(x, y);
        printf("%c", FOOD);
        set_color(7);
        break;
    }
}
//读取并显示最高分
void scoreandtips() {
    gotoxy(MIN_X, MIN_Y - 1);
    set_color(11);
    printf("分数：%d   长度：%d   最高分：%d", 
           score, curLen, highScore);
    set_color(7);
}
//蛇的移动
void snakemove() {
    Point newhead = {
        snake[head].x + faceX,
        snake[head].y + faceY
    };
    bool ate = (newhead.x == food.x && newhead.y == food.y);
    head = (head + 1) % MAXLEN;
    snake[head] = newhead;
    valid[newhead.x][newhead.y] = 0;

    set_color(14);
    gotoxy(newhead.x, newhead.y);
    printf("%c", BLOCK);
    set_color(7);

    Point old = snake[tail];
    gotoxy(old.x, old.y);
    printf(" ");
    valid[old.x][old.y] = 1;
    tail = (tail + 1) % MAXLEN;
    
    if(ate) {
        score += scoreGain;
        speedup();
        scoreandtips();
        createfood();
    }

}
//游戏结束
void gameover(int finalScore) {
    system("cls");
    set_color(12);
    if (finalScore > highScore) {
        printf("创纪录啦！最高分被你刷新啦：%d\n\n", finalScore);
        // 把新的最高分写回文件
        FILE *f = fopen("save.txt", "w");
        if (f) {
            fprintf(f, "%d", finalScore);
            fclose(f);
        }
        highScore = finalScore;
    } else {
        printf("游戏结束，你的得分：%d，\n距离最高分还差：%d\n\n",
               finalScore, highScore - finalScore);
    }
    set_color(7);
    printf("1. 重玩   2. 退出游戏\n");
    char ch;
    while (1) {
        ch = _getch();
        if (ch == '1') {
            game();   // 重玩
            return;
        } else if (ch == '2') {
            exit(0);
        }
    }
}
//键盘控制
void keyboardControl() {
    while(_kbhit()) {
        int ch = _getch();
        if (ch == 0 || ch == 224) {
            int code = _getch();
            switch (code) {
                case 72:  // ↑
                    if (faceY != 1) { faceX = 0; faceY = -1; }
                    break;
                case 80:  // ↓
                    if (faceY != -1){ faceX = 0; faceY = 1;  }
                    break;
                case 75:  // ←
                    if (faceX != 1) { faceX = -1; faceY = 0; }
                    break;
                case 77:  // →
                    if (faceX != -1){ faceX = 1; faceY = 0;  }
                    break;
                case 59:  // F1
                    speedup(); break;
                case 60:  // F2
                    speeddown(); break;
            }
        }
        else if (ch == 27) {         // ESC
            gameover(score);
        }
    }
}
// 游戏说明
void explanation() {
    system("cls");
    set_color(7);
    gotoxy(MIN_X, MIN_Y);
    printf("游戏说明：\n");
    printf("1. 使用 ↑ ↓ ← → 控制蛇的移动方向，不能直接掉头。\n");
    printf("2. 吃到 %c 会获得分数，吃完后食物会重新生成。\n", FOOD);
    printf("3. 按 F1 加速（-30ms）并 +2 分，按 F2 减速（+30ms）并 +2 分。\n");
    printf("4. 蛇撞墙或咬到自己时游戏结束。\n");
    printf("5. ESC 随时退出到失败界面。\n\n");
    printf("当前最高分：%d\n\n", highScore);
    printf("按任意键返回……");
    _getch();
}
//打印蛇的字符画
void printsnake() {
    const char *snakeArt[] = {
        "                   /^\\/^\\                   ",
        "                 _|__|  O|>                 ",
        "        \\/     /~     \\_/ \\                ",
        "         \\____|__________/  \\               ",
        "              \\_______      \\              ",
        "                      `\\     \\             ",
        "                        |     |             ",
        "                         \\____\\            "
    };
    int lines = sizeof(snakeArt) / sizeof(snakeArt[0]);

    set_color(10);
    for (int i = 0; i < lines; i++) {
        gotoxy(0, 2 + i);
        printf("%s", snakeArt[i]);
    }
    set_color(7);
}
//游戏主体逻辑
void game() {
    File_out();
    score = 0;
    sleepTime = 350;
    scoreGain = 1;
    createMap();
    scoreandtips();
    initsnake();
    createfood();
    DWORD lastMove = GetTickCount();
    while (1) {
        keyboardControl();
        DWORD now = GetTickCount();

        double interval = (faceX != 0) ? sleepTime : sleepTime * 1.2;
        if ((double)(now - lastMove) >= interval) {
            lastMove = now;
            snakemove();
            scoreandtips();

            if (cantcrosswall() || biteself()) {
                gameover(score);
                break;
            }
        }
        Sleep(10);
    }
}
//游戏欢迎界面
void welcometogame() {
    char ch;
    while(1) {
        system("cls");
        printsnake();
        //选择界面
        set_color(11);
        gotoxy(10, 0);
        printf("贪吃蛇游戏");
        gotoxy(10, 11);
        printf("1. 开始游戏\n");
        gotoxy(10, 12);
        printf("2. 查看最高分\n");
        gotoxy(10, 13);
        printf("3. 退出游戏\n");
        set_color(7);
        //选择
        ch = _getch();
        switch(ch) {
            case '1':
                game();
                break;
            case '2':
                explanation();
                break;
            case '3':
            case 27: //ESC
                exit(0);
                break;
            default:
                gotoxy(10, 17);
                set_color(12);
                printf("请输入 1~3 之间的数字");
                set_color(7);
                Sleep(800);
        }
    }
}
int main() {
    SetConsoleOutputCP(CP_UTF8);
    SetConsoleCP(CP_UTF8);
    File_out();
    welcometogame();
    return 0;
}